﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace SL8.SL.Behaviors
{
    public class KeyEventInvokeBehavior : Behavior<FrameworkElement>
    {
        private List<MethodDescriptor> methodDescriptors = new List<MethodDescriptor>();

        public Key Key
        {
            get { return (Key)GetValue(KeyProperty); }
            set { SetValue(KeyProperty, value); }
        }

        public static readonly DependencyProperty MethodNameProperty = DependencyProperty.Register("MethodName",
            typeof(string),
            typeof(KeyEventInvokeBehavior),
            new PropertyMetadata(new PropertyChangedCallback(KeyEventInvokeBehavior.OnMethodNameChanged)));

        public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register("TargetObject",
            typeof(object),
            typeof(KeyEventInvokeBehavior),
            new PropertyMetadata(new PropertyChangedCallback(KeyEventInvokeBehavior.OnTargetObjectChanged)));

        public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register("Parameter",
            typeof(object),
            typeof(KeyEventInvokeBehavior),
            null);

        public static readonly DependencyProperty KeyProperty = DependencyProperty.Register("Key",
            typeof(Key),
            typeof(KeyEventInvokeBehavior),
            new PropertyMetadata(Key.Enter));

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(KeyEventInvokeBehavior), null);

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public string MethodName
        {
            get
            {
                return (string)base.GetValue(KeyEventInvokeBehavior.MethodNameProperty);
            }
            set
            {
                base.SetValue(KeyEventInvokeBehavior.MethodNameProperty, value);
            }
        }

        public object TargetObject
        {
            get
            {
                return base.GetValue(KeyEventInvokeBehavior.TargetObjectProperty);
            }
            set
            {
                base.SetValue(KeyEventInvokeBehavior.TargetObjectProperty, value);
            }
        }

        public object Parameter
        {
            get
            {
                return base.GetValue(KeyEventInvokeBehavior.ParameterProperty);
            }
            set
            {
                base.SetValue(KeyEventInvokeBehavior.ParameterProperty, value);
            }
        }

        private static void OnMethodNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ((KeyEventInvokeBehavior)sender).UpdateMethodInfo();
        }

        private static void OnTargetObjectChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ((KeyEventInvokeBehavior)sender).UpdateMethodInfo();
        }

        protected override void OnAttached()
        {
            this.AssociatedObject.KeyUp += new KeyEventHandler(AssociatedObject_KeyUp);
        }

        private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == this.Key)
            {
                this.InvokeMethod(sender, e);
                this.InvokeCommand();
            }
        }

        private void UpdateMethodInfo()
        {
            this.methodDescriptors.Clear();
            if (this.TargetObject != null && !string.IsNullOrEmpty(this.MethodName))
            {
                foreach (MethodInfo info in this.TargetObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (this.IsMethodValid(info))
                    {
                        ParameterInfo[] parameters = info.GetParameters();
                        if (this.AreMethodParamsValid(parameters))
                        {
                            this.methodDescriptors.Add(new MethodDescriptor(info, parameters));
                        }
                    }
                }

                this.methodDescriptors = this.methodDescriptors.OrderByDescending(o => o.ParameterCount).ToList<MethodDescriptor>();
            }
        }

        private bool IsMethodValid(MethodInfo method)
        {
            if (!string.Equals(method.Name, this.MethodName, StringComparison.Ordinal))
            {
                return false;
            }

            if (method.ReturnType != typeof(void))
            {
                return false;
            }

            return true;
        }

        private bool AreMethodParamsValid(ParameterInfo[] methodParams)
        {
            if (methodParams.Length == 2)
            {
                if (methodParams[0].ParameterType != typeof(object))
                {
                    return false;
                }

                if (!typeof(EventArgs).IsAssignableFrom(methodParams[1].ParameterType))
                {
                    return false;
                }
            }
            else if (methodParams.Length == 1 || methodParams.Length == 0)
            {
                return true;
            }

            return false;
        }

        private MethodDescriptor FindBestMethod(object eventParameter)
        {
            return this.methodDescriptors.FirstOrDefault(methodDescriptor =>
            {
                if (methodDescriptor.ParameterCount == 2)
                {
                    if (methodDescriptor.Parameters[1].ParameterType.IsAssignableFrom(eventParameter.GetType()))
                    {
                        return true;
                    }
                }
                else if (methodDescriptor.ParameterCount == 1)
                {
                    if (this.Parameter == null)
                    {
                        return true;
                    }
                    else
                    {
                        if (methodDescriptor.Parameters[0].ParameterType == typeof(object)
                            || methodDescriptor.Parameters[0].ParameterType == this.Parameter.GetType())
                        {
                            return true;
                        }
                    }

                }
                else if (methodDescriptor.ParameterCount == 0)
                {
                    //if (this.Parameter != null)
                    //{
                    //    return false;
                    //}

                    return true;
                }

                return false;
            });
        }

        private void InvokeMethod(object sender, object eventParameter)
        {
            if (base.AssociatedObject != null)
            {
                MethodDescriptor descriptor = this.FindBestMethod(eventParameter);

                if (descriptor != null)
                {
                    ParameterInfo[] parameters = descriptor.Parameters;
                    if (parameters.Length == 0)
                    {
                        descriptor.MethodInfo.Invoke(this.TargetObject, null);
                    }
                    else if (parameters.Length == 2
                        && base.AssociatedObject != null
                        && eventParameter != null
                        && parameters[0].ParameterType.IsAssignableFrom(base.AssociatedObject.GetType())
                        && parameters[1].ParameterType.IsAssignableFrom(eventParameter.GetType()))
                    {
                        descriptor.MethodInfo.Invoke(this.TargetObject, new object[] { base.AssociatedObject, eventParameter });
                    }
                    else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(object))
                    {
                        descriptor.MethodInfo.Invoke(this.TargetObject, new object[] { this.Parameter });
                    }
                    else if (parameters.Length == 1 && this.Parameter != null && parameters[0].ParameterType.IsAssignableFrom(this.Parameter.GetType()))
                    {
                        descriptor.MethodInfo.Invoke(this.TargetObject, new object[] { this.Parameter });
                    }
                    else if (parameters.Length == 1 && this.Parameter == null && !parameters[0].ParameterType.IsValueType)
                    {
                        descriptor.MethodInfo.Invoke(this.TargetObject, new object[] { null });
                    }
                    else
                    {
                        throw new ArgumentException(string.Format("在{1}中的方法{0}找不到匹配的参数", new object[] { this.MethodName, this.TargetObject.GetType().Name }));
                    }
                }
                else if (this.TargetObject != null)
                {
                    throw new ArgumentException(string.Format("在{1}中找不到匹配的方法：{0}", new object[] { this.MethodName, this.TargetObject.GetType().Name }));
                }
            }
        }

        private void InvokeCommand()
        {
            if (this.Command != null)
            {
                if (this.Command.CanExecute(this.Parameter))
                {
                    this.Command.Execute(this.Parameter);
                }
            }
        }
    }
}
